See https://github.com/fpinscala/fpinscala/wiki/Chapter-2:-Getting-started for official notes.
Recursive function calls can be tail recursive if the last call of the function can be returns immediately. Scala will convert this into a while loop, and there will be not stack frame exhaustion. An example of a recursive call that is not tail recursive:
1 + go(n, acc)
In this case, even after having evaluated go(n, acc) the funcion result must be returned in order to be evaluated in the stack above. 
A tail call elimination can be made when there is no subsequent evaluation.
An annotation @annotation.tailrec can be added before the function call in order to catch any recursive functions that should but cannot be tail call eliminated.
In [9]:
    
def fibTail(n: Int): Int = {
    @annotation.tailrec
    def go(n: Int, current: Int, fibCurrent: Int, fibPrevious: Int): Int = {
      if(n == current) return(fibCurrent)
      go(n, current + 1, fibCurrent + fibPrevious, fibCurrent)
    }
    if(n == 0) return(0)
     
    go(n, 1, 1, 0)
  }
    
    
In [4]:
    
fibTail(11)
    
    
Markus Fib Note that he uses three arguments and counts down rather than up.
In [76]:
    
def fib(n: Int): Int = {
      @annotation.tailrec
      def loop(n: Int, currentFib: Int, nextFib: Int): Int = 
          if (n == 0) currentFib else loop(n-1, nextFib, currentFib + nextFib)
    
      if (n < 0) throw new IllegalArgumentException("fibonacci argument must be greater than 0")
      loop(n, 0, 1)
  }
    
    
In [16]:
    
(0 to 5).map(fib)
    
    
In [14]:
    
(0 to 5).map(fibTail)
    
    
In [11]:
    
def fibRunar(n: Int): Int = {
      @annotation.tailrec
      def go(n: Int, acc: Int, res: Int): Int =
          if (n == 0) res
          else go(n - 1, acc + res, acc)
      go(n, 1, 0)
  }
    
    
In [ ]:
    
    
In [24]:
    
def isSorted[A](as: Array[A], ordered: (A,A) => Boolean): Boolean = {
    
    @annotation.tailrec
    def loop(n: Int): Boolean = {
        if(n >= as.length) true
        else if(ordered(as(n-1), as(n))) loop(n+1)
        else false
    }
    if(as.length == 1) return(true)
    loop(1)
}
    
    
In [25]:
    
isSorted(Array(1), (x: Int,y: Int) => x <= y)
    
    
In [26]:
    
isSorted(Array(1,2,3), (x: Int,y: Int) => x <= y)
    
    
In [27]:
    
isSorted(Array(1,6,5), (x: Int,y: Int) => x <= y)
    
    
In [28]:
    
isSorted(Array("AB", "B"), (x: String, y: String) => x <= y)
    
    
In [21]:
    
@annotation.tailrec
final def isSortedMarkus[A](as: Array[A], gt: (A,A) => Boolean): Boolean = {
      if (as.length < 2) true
      else if (gt(as.head, as.tail.head)) false
      else isSortedMarkus(as.tail, gt)
  }
    
    
In [83]:
    
isSortedMarkus(Array(1,6,5), (x: Int,y: Int) => x <= y)
    
    
In [86]:
    
load.ivy("com.storm-enroute" %% "scalameter" % "0.7")
    
    
    
In [ ]:
    
    
Currying functions changes the structure of how you have to call it. If we look at a function that takes two arguments: f(a: A, b: B): C and curry it, it will take one argument but return a function: g(a: A): B => C. Partially applying a function on the other hand will take a function the will take say two arguments f(a: A, b: B): C, and fix one of the argument b so the remaining function takes on argument: h(a: A): C.
In [77]:
    
def curry[A,B,C](f: (A, B) => C): A => (B => C) = {
    a => b => f(a,b)
}
    
    
In [80]:
    
def add(x: Int, y: Int) = x + y
    
    
In [82]:
    
    
    
In [ ]:
    
    
In [36]:
    
add(2,3)
    
    
In [37]:
    
val add3to = curry(add)(3)
    
    
In [39]:
    
add3to(4)
    
    
Curry implementation that's build in to scala:
In [47]:
    
val f = (a: Int, b: Int) => a + b
  
val curf = f.curried
    
    
In [57]:
    
val bla = curf _
    
    
Hmm? how about this one!
In [56]:
    
bla()(3)(3)
    
    
In [58]:
    
def uncurry[A,B,C](f: A => B => C): (A, B) => C = {
    (a, b) => f(a)(b)
}
    
    
In [59]:
    
val uncurriedAdd = uncurry(curry(add))
    
    
In [61]:
    
uncurriedAdd(2,3)
    
    
In [71]:
    
def compose[A, B, C](f: B => C, g: A => B): A => C = 
    a => f(g(a))
    
    
In [72]:
    
def addQuotes(d: Int): String = "'" + d + "'"
    
    
In [73]:
    
def add3AndCompose = compose(addQuotes, add3to)
    
    
In [74]:
    
add3AndCompose(4)
    
    
In [ ]: